package com.tlvip.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * <p>文件描述：</p>
 * <p>版权所有： 版权所有(C)2016-2099</p>
 * <p>Life is short,we need passion</p>
 * <p>内容摘要： </p>
 * <p>其他说明： </p>
 * Date 2019-12-16</p>
 * Author mac
 *
 * @version 1.0
 */
// 同步：单线程select，客户端、服务端都注册在一个selector上
// 非阻塞：服务端处理完一个客户端SocketChannel连接之后，
//       不会阻塞在read上，立即返回并阻塞在Selector.select()，等待下一个客户端事件（connect、write等）发生
//       select与accept都是阻塞的，这两个方法都会在事件发生时被selector统一协调调用，也就不阻塞了
//       read方法是非阻塞的，因为一个write发生后，数据已经进入缓冲区，selector取到事件后，通知read，所以就非阻塞了
// 借助selector，可以让一个SeverSocketChannel响应大量客户端SocketChannel请求，而且在单线程模式下
public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必须配置为非阻塞才能往selector上注册，否则会报错，selector模式本身就是非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(9000));
        // 创建一个选择器并将serverSocketChannel注册到它上面
        Selector selector = Selector.open();
        // 把channel注册到selector上，并且selector对客户端accept连接操作感兴趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            System.out.println("等待事件发生。。");
            // 轮询监听key，select是阻塞的，accept()也是阻塞的
            // 如果客户端有连接操作、写数据操作，select就会被唤醒
            selector.select();
            System.out.println("有事件发生了。。");
            // 有客户端请求，被轮询监听到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //删除本次已处理的key，防止下次select重复处理
                handle(key);
                it.remove();
            }
        }
    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有客户端连接事件发生了。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            // 此处accept方法是阻塞的，但是这里因为是发生了连接事件，所以这个方法会马上执行完
            // 客户端的SocketChannel与这个SocketChannel可以看成一个，本来连接就是一个.
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);

            // 通过Selector监听Channel时对读事件感兴趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("有客户端数据可读事件发生了。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.clear();

            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            sc.write(bufferToWrite);
            // 重新注册读写事件
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            sc.close();
        }
    }
}
